<script>
window.loadOnigasmFlag = false;
</script>

<script setup>
import { ref, onMounted, onBeforeUnmount, watch, watchEffect } from "vue";

import "monaco-editor-core";
import "monaco-volar";

import * as onigasm from "onigasm";
import onigasmWasm from "onigasm/lib/onigasm.wasm?url";
import * as monaco from "monaco-editor-core";
import { loadTheme, loadGrammars } from "monaco-volar";
import * as volar from "@volar/monaco";
import editorWorker from "monaco-editor-core/esm/vs/editor/editor.worker?worker";
import vueWorker from "monaco-volar/vue.worker?worker";
import jsonWorker from "./json/json.worker?worker";
import "./json/monaco.contribution1";

import { readClipText } from "@/utils/index";

function loadOnigasm() {
  if (!window.loadOnigasmFlag) {
    loadOnigasmFlag = true;
  } else {
    return;
  }
  return onigasm.loadWASM(onigasmWasm);
}

function loadMonacoEnv(takeoverMode = true) {
  let initialized = false;
  setup();
  monaco.languages.onLanguage("vue", setup);
  if (takeoverMode) {
    monaco.languages.onLanguage("javascript", setup);
    monaco.languages.onLanguage("typescript", setup);
  }

  async function setup() {
    if (initialized) {
      return;
    }
    console.log(" loadMonacoEnv setup.....");
    initialized = true;
    self.MonacoEnvironment ??= {};
    self.MonacoEnvironment.getWorker ??= () => new editorWorker();

    const getWorker = self.MonacoEnvironment.getWorker;

    self.MonacoEnvironment.getWorker = (_, label) => {
      if (label === "vue") {
        return new vueWorker();
      }
      if (label === "json") {
        return new jsonWorker();
      }
      return getWorker();
    };

    const worker = monaco.editor.createWebWorker({
      moduleId: "vs/language/vue/vueWorker",
      label: "vue",
      createData: {},
    });
    const languageId = takeoverMode
      ? ["vue", "javascript", "typescript", "json"]
      : ["vue"];
    const getSyncUris = () =>
      monaco.editor.getModels().map((model) => model.uri);
    volar.editor.activateMarkers(
      worker,
      languageId,
      "vue",
      getSyncUris,
      monaco.editor
    );
    volar.editor.activateAutoInsertion(
      worker,
      languageId,
      getSyncUris,
      monaco.editor
    );
    await volar.languages.registerProvides(
      worker,
      languageId,
      getSyncUris,
      monaco.languages
    );
  }
}

defineOptions({ name: "CodeEditor" });

const props = defineProps({
  type: {
    type: String,
    // code | diff
    default: "code",
  },
  language: {
    type: String,
    // vue | javascript | typescript | json
    default: "vue",
  },
  code: {
    type: String,
    requied: true,
  },
  diffCode: {
    type: String,
    default: "",
  },
  autoPasteClip: {
    type: Boolean,
    default: false,
  },
});

const emit = defineEmits(["update:code", "update:diffCode"]);

const containerRef = ref(null);
let editorInstance = null;
let codeModel = null;
let diffModel = null;
const codeValue = ref("");

function mountEditor() {
  editorInstance?.dispose();
  const suffix = {
    javascript: "js",
    typescript: "ts",
    vue: "vue",
  };
  Promise.all([loadMonacoEnv(), loadOnigasm(), loadTheme(monaco.editor)]).then(
    ([, , theme]) => {
      codeModel?.dispose();
      diffModel?.dispose();
      const options = {
        language: props.language,
        theme: theme.dark,
        automaticLayout: true,
        scrollBeyondLastLine: false,
        minimap: {
          enabled: false,
        },
        inlineSuggest: {
          enabled: false,
        },
        "semanticHighlighting.enabled": true,
      };
      if (props.type === "code") {
        codeModel = monaco.editor.createModel(
          props.code,
          props.language,
          props.language !== "json"
            ? "file:///demo." + suffix[props.language]
            : null
        );
        options.model = codeModel;
        codeModel.onDidChangeContent(() => {
          codeValue.value = codeModel.getValue();
          emit("update:code", codeValue.value);
        });
        watch(
          () => props.code,
          () => {
            if (props.code !== codeValue.value) {
              codeModel.setValue(props.code);
            }
          }
        );
        editorInstance = monaco.editor.create(containerRef.value, options);
        editorInstance.onDidFocusEditorText(async (e) => {
          if (props.autoPasteClip) {
            const modelCode = codeModel.getValue();
            if (!modelCode) {
              const clipCode = await readClipText();
              if (clipCode) {
                codeModel.setValue(clipCode);
              }
            }
          }
        });
      } else {
        editorInstance = monaco.editor.createDiffEditor(
          containerRef.value,
          options
        );
      }
      loadGrammars(monaco, editorInstance);

      if (props.type === "diff") {
        diffModel = monaco.editor.createModel(
          props.diffCode,
          props.language,
          props.language !== "json"
            ? "file:///demo." + suffix[props.language]
            : null
        );
        watchEffect(() => {
          diffModel.setValue(props.diffCode);
        });
        editorInstance.setModel({
          // oldValue为以前的值
          original: monaco.editor.createModel(props.code, props.language),
          // modified为新的值
          modified: diffModel,
        });
      }
    }
  );
}

function unmountEditor() {
  editorInstance?.dispose();
  codeModel?.dispose();
  diffModel?.dispose();
  editorInstance = null;
  codeModel = null;
  diffModel = null;
}

watch(
  () => props.type,
  () => {
    mountEditor();
  }
);

onMounted(() => {
  mountEditor();
});

onBeforeUnmount(() => {
  unmountEditor();
});
</script>

<template>
  <div class="editor" ref="containerRef"></div>
</template>

<style>
.editor {
  width: 100%;
  height: 100%;
}
</style>
